package com.sintatico;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Stack;

public class SintaticoModel implements Serializable {

	private static final long serialVersionUID = 1L;

	private TabelaAnalise tabela;

	private final Character SOURCE = 'S';
	private final Character EPSILON = 'E';
	private final Character DOLLAR = '$';

	private Stack<Character> pilha;
	private Stack<Character> entrada;

	private ArrayList<String> actions;
	private ArrayList<String> actionsPilha;
	private ArrayList<String> actionsEntrada;

	private int iteracao = 0;

	public Stack<Character> getPilha() {
		if (pilha == null) {
			pilha = new Stack<Character>();
		}
		return pilha;
	}

	public Stack<Character> getEntrada() {
		if (entrada == null) {
			entrada = new Stack<Character>();
		}
		return entrada;
	}

	public ArrayList<String> getActions() {
		if (actions == null) {
			actions = new ArrayList<String>();
		}
		return actions;
	}

	public ArrayList<String> getActionsPilha() {
		if (actionsPilha == null) {
			actionsPilha = new ArrayList<String>();
		}
		return actionsPilha;
	}

	public ArrayList<String> getActionsEntrada() {
		if (actionsEntrada == null) {
			actionsEntrada = new ArrayList<String>();
		}
		return actionsEntrada;
	}

	public TabelaAnalise getTabela() {
		if (tabela == null) {
			tabela = new TabelaAnalise();
			tabela.fillActions();
		}
		return tabela;
	}

	public int getIteracao() {
		return iteracao;
	}

	public void setIteracao(int iteracao) {
		this.iteracao = iteracao;
	}

	public void resetaAnalise() {
		getPilha().clear();
		getEntrada().clear();
		getPilha().push(DOLLAR);
		getEntrada().push(DOLLAR);
	}

	public boolean analisaSentenca(String sentenca) {
		int iteracoes = 1;
		resetaAnalise();
		getPilha().push(SOURCE);

		boolean valida = true;

		for (int i = sentenca.length() - 1; i >= 0; i--) {
			char letra = sentenca.charAt(i);
			getEntrada().push(letra);
		}

		getActionsPilha().add(getPilha().toString());
		getActionsEntrada().add(reverseStack(getEntrada()));

		while (!getPilha().empty() && !getEntrada().empty()) {

			/* pilha e entrada vazias -> sucesso lol */
			if (getPilha().peek().equals(DOLLAR) && getEntrada().peek().equals(DOLLAR)) {
				setIteracao(iteracoes++);

				getActionsPilha().add(getPilha().toString());
				getActionsEntrada().add(reverseStack(getEntrada()));
				getActions().add(formatSuccessAction(getIteracao()));
				break;
			}

			/* entrada vazia */
			if (!getPilha().peek().equals(DOLLAR) && getEntrada().peek().equals(DOLLAR)) {
				setIteracao(iteracoes++);

				getActionsPilha().add(getPilha().toString());
				getActionsEntrada().add(reverseStack(getEntrada()));
				getActions().add(formatErrorAction(getIteracao()));
				valida = false;
				break;
			}

			/* pilha vazia */
			if (getPilha().peek().equals(DOLLAR) && !getEntrada().peek().equals(DOLLAR)) {
				setIteracao(iteracoes++);

				getActionsPilha().add(getPilha().toString());
				getActionsEntrada().add(reverseStack(getEntrada()));
				getActions().add(formatErrorAction(getIteracao()));
				valida = false;
				break;
			}

			/* topo das pilhas iguais */
			if (getPilha().peek().equals(getEntrada().peek())) {
				getActions().add(String.format("Lendo %c", getPilha().peek()));

				getPilha().pop();
				getEntrada().pop();

				getActionsPilha().add(getPilha().toString());
				getActionsEntrada().add(reverseStack(getEntrada()));

				setIteracao(iteracoes++);
			}

			/* pega topo da pilha */
			Character topPilha = getPilha().peek();

			/* pega topo da entrada */
			Character topEntrada = getEntrada().peek();

			/* eh um Nao Terminal */
			if (getTabela().getAnalise().containsKey(topPilha)) {
				getPilha().pop();

				setIteracao(iteracoes++);

				HashMap<Character, ArrayList<Character>> map = getTabela().getAnalise().get(topPilha);
				ArrayList<Character> list = map.get(topEntrada);

				/* erro */
				if (list == null) {
					getActionsPilha().add(getPilha().toString());
					getActionsEntrada().add(reverseStack(getEntrada()));
					getActions().add(formatErrorAction(getIteracao()));
					valida = false;
					break;
				}

				/* empilha os estados */
				for (Character character : list) {
					if (!character.equals(EPSILON)) {
						getPilha().push(character);
					}
				}

				/* grava a acao */
				getActionsPilha().add(getPilha().toString());
				getActionsEntrada().add(reverseStack(getEntrada()));
				getActions().add(formatAction(list, topPilha));
			}
		}

		return valida;
	}

	private String reverseStack(Stack<Character> stack) {
		StringBuilder builder = new StringBuilder(stack.toString());
		builder.reverse().setCharAt(0, '[');
		builder.setCharAt(builder.length() - 1, ']');
		return builder.toString();
	}

	public String formatAction(ArrayList<Character> list, Character topPilha) {
		StringBuilder states = new StringBuilder();
		for (Character character : list) {
			states.append(character);
		}
		return String.format("%c -> %s", topPilha, states.reverse().toString());
	}

	public String formatSuccessAction(int iter) {
		return String.format("Aceito em %d iteracoes", iter);
	}

	public String formatErrorAction(int iter) {
		return String.format("Rejeitado em %d iteracoes", iter);
	}

}
